use std::{
    fs,
    path::{Path, PathBuf},
};

use anyhow::{Context, Result};

use crate::domain::NodeConf;

#[allow(dead_code)]
pub struct NodeConfService;

#[allow(dead_code)]
impl NodeConfService {
    pub fn read_nodes_config(folder: impl AsRef<Path>) -> Result<Vec<NodeConf>> {
        let folder = folder.as_ref();
        let path = folder.join("nodesconf.json");

        if !path.exists() {
            return Ok(Vec::new());
        }

        let file = fs::File::open(&path)
            .with_context(|| format!("打开 nodesconf.json 失败: {}", path.display()))?;
        let reader = std::io::BufReader::new(file);

        let mut configs: Vec<NodeConf> = serde_json::from_reader(reader)
            .with_context(|| format!("解析 nodesconf.json 失败: {}", path.display()))?;

        for conf in &mut configs {
            if conf.wave_type.is_empty() {
                conf.wave_type = "声波".to_string();
            }
        }

        Ok(configs)
    }

    pub fn generate_station_folder_name(ip_address: &str, board_id: i32) -> String {
        if ip_address.is_empty() {
            return format!("192.168.1.{board_id}");
        }

        let parts = ip_address.split('.').take(3).collect::<Vec<_>>();
        if parts.len() < 3 {
            return format!("192.168.1.{board_id}");
        }
        format!("{}.{}", parts.join("."), board_id)
    }

    pub fn get_station_folder_name(
        base_directory: impl AsRef<Path>,
        node_conf: &NodeConf,
    ) -> String {
        let base_directory = base_directory.as_ref();
        if let Some(existing) =
            Self::find_existing_station_folder(base_directory, node_conf.boardid)
        {
            return existing;
        }
        Self::generate_station_folder_name(node_conf.ipaddr_str(), node_conf.boardid)
    }

    pub fn find_node_conf_for_folder<'a>(
        folder_name: &str,
        node_confs: &'a [NodeConf],
    ) -> Option<&'a NodeConf> {
        if folder_name.is_empty() {
            return None;
        }

        if let Some(node) = node_confs
            .iter()
            .find(|node| node.ipaddr.as_deref() == Some(folder_name))
        {
            return Some(node);
        }

        let mut parts = folder_name.split('.').collect::<Vec<_>>();
        if parts.len() != 4 {
            return None;
        }

        let board_id = parts.pop()?.parse::<i32>().ok()?;
        let prefix = parts.join(".");

        node_confs.iter().find(|node| {
            node.boardid == board_id
                && node
                    .ipaddr
                    .as_deref()
                    .map_or(false, |ip| ip.starts_with(&prefix))
        })
    }

    fn find_existing_station_folder(base_directory: &Path, board_id: i32) -> Option<String> {
        let entries = fs::read_dir(base_directory).ok()?;
        let mut candidates: Vec<String> = entries
            .filter_map(|entry| {
                let dir = entry.ok()?;
                if !dir.file_type().ok()?.is_dir() {
                    return None;
                }
                dir.file_name().to_str().map(|s| s.to_owned())
            })
            .filter(|name| name.ends_with(&format!(".{board_id}")))
            .collect();

        if candidates.is_empty() {
            return None;
        }

        candidates.sort();

        let preferred = format!("0.0.0.{board_id}");
        if let Some(folder) = candidates
            .iter()
            .find(|name| name.as_str() == preferred.as_str())
        {
            return Some(folder.clone());
        }

        candidates.first().cloned()
    }

    #[allow(dead_code)]
    pub fn collect_station_folders(base_directory: impl AsRef<Path>) -> Result<Vec<PathBuf>> {
        let base_directory = base_directory.as_ref();
        if !base_directory.exists() {
            return Ok(Vec::new());
        }

        let mut folders = Vec::new();
        for entry in fs::read_dir(base_directory)? {
            let entry = entry?;
            if entry.file_type()?.is_dir() {
                folders.push(entry.path());
            }
        }
        Ok(folders)
    }
}
